# app/service/game_service.py   主要为有返回值的接收请求获取信息
from utils.token_util import get_user_id_by_token
from db.redis_part import xibie_redis
from db.mongo_part import xibie_mongo
from db.mysql_part import get_mysql_session
from db.models import Pet, PetType, RaceGroupPetBanHide, RaceGroupPetTypeHide, Suit
from app.config import (
    REDIS_USER_PREFIX,
    REDIS_GAME_PREFIX, REDIS_GAME_EXPIRE
)
from app.handler.ws_manage import ws_manager, generate_event
from app.handler.delay_manage import delay_manager
from app.exception.cus_exception import InvalidActionException, InvalidGameException
import json
from datetime import datetime
import random
import asyncio
from sqlalchemy import select, func, and_, or_
from utils.log_util import logger

def get_game_id(token: str):
    user_id = get_user_id_by_token(token)
    game_id = xibie_redis.hget(f'{REDIS_USER_PREFIX}{user_id}', 'gameId')
    if game_id is None:
        raise InvalidGameException('房间不存在', user_id)
    elif not xibie_redis.exists(f'{REDIS_GAME_PREFIX}{game_id}'):
        xibie_redis.hdel(f'{REDIS_USER_PREFIX}{user_id}', 'gameId')
        raise InvalidGameException('房间不存在', user_id)
    return game_id

def get_game_side(game_id, user_id):
    """获取用户是蓝方还是红方

    Args:
        game_id (_type_): _description_
        user_id (_type_): _description_

    Returns:
        _type_: _description_
    """
    game_info = xibie_redis.hgetall(f'{REDIS_GAME_PREFIX}{game_id}')
    if 'player1' in game_info and game_info['player1'] == user_id:
        return 'player1'
    elif 'player2' in game_info and game_info['player2'] == user_id:
        return 'player2'
    else:
        return 'watcher'
    
def get_curr_phase_info(game_id):
    """获取当前阶段信息

    Args:
        game_id (_type_): _description_

    Returns:
        _type_: _description_
    """
    curr_phase = xibie_redis.lindex(f"{REDIS_GAME_PREFIX}{game_id}-phase", 0)
    if not curr_phase:
        return {}
    return json.loads(curr_phase)

def insert_phase_by_rule(phase_list: list[dict], rule: str):
    """根据规则插入game phase

    Args:
        phase_list (list[dict]): _description_
        rule (str): _description_
    """
    if rule == "rule1":  # 双方指定第三轮使用的属性
        phase_list.insert(4, {
            "operation": "pick",
            "item": "pet_type",
            "side": "player1",
            "count": 1
        })
        phase_list.insert(6, {
            "operation": "pick",
            "item": "pet_type",
            "side": "player2",
            "count": 1
        })
    if rule == "rule2":  # 双方开局各禁用对手一个套装
        phase_list.insert(0, {
            "operation": "ban",
            "item": "suit",
            "side": "player1",
            "count": 1
        })
        phase_list.insert(1, {
            "operation": "ban",
            "item": "suit",
            "side": "player2",
            "count": 1
        })

    if rule == "rule4":  # 双方开局各全局禁用一个属性
        phase_list.insert(0, {
            "operation": "ban",
            "item": "pet_type",
            "side": "player1",
            "count": 1
        })
        phase_list.insert(1, {
            "operation": "ban",
            "item": "pet_type",
            "side": "player2",
            "count": 1
        })

def init_config_by_rule(game_id, rule):
    """根据规则调整game配置项

    Args:
        game_id (_type_): _description_
        rule (_type_): _description_
    """
    if rule == "rule3":  # 双方抽到的属性不会重复
        xibie_redis.hset(f"{REDIS_GAME_PREFIX}{game_id}", "uniqueType", 1)
    if rule == "rule5":  # 双方重抽和暗手次数变为各共计五次
        xibie_redis.hset(f"{REDIS_GAME_PREFIX}{game_id}", "player1BothCount", 5)
        xibie_redis.hset(f"{REDIS_GAME_PREFIX}{game_id}", "player2BothCount", 5)
        xibie_redis.hdel(f"{REDIS_GAME_PREFIX}{game_id}", "player1RollCount")
        xibie_redis.hdel(f"{REDIS_GAME_PREFIX}{game_id}", "player2RollCount")
        xibie_redis.hdel(f"{REDIS_GAME_PREFIX}{game_id}", "player1HideCount")
        xibie_redis.hdel(f"{REDIS_GAME_PREFIX}{game_id}", "player2HideCount")
    if rule == "rule6":  # 双方只能使用衍生属性的精灵
        xibie_redis.hset(f"{REDIS_GAME_PREFIX}{game_id}", "onlyDerive", 1)

def generate_phase(game_id):
    """初始化game phase列表

    Args:
        game_id (_type_): _description_
    """
    rule_first = xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", "ruleFirst")
    rule_second = xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", "ruleSecond")
    phase_list = []
    for i in range(6):
        phase_list.append({
            "operation": "pick",
            "item": "pet",
            "side": "player1",
            "count": i + 1
        })
        phase_list.append({
            "operation": "pick",
            "item": "pet",
            "side": "player2",
            "count": i + 1
        })
    phase_list.insert(6, {
        "operation": "pick",
        "item": "suit",
        "side": "player1",
        "count": 1
    })
    phase_list.insert(7, {
        "operation": "pick",
        "item": "suit",
        "side": "player2",
        "count": 1
    })
    insert_phase_by_rule(phase_list, rule_first)
    insert_phase_by_rule(phase_list, rule_second)
    phase_list.insert(0, {
        "operation": "readyStart",
        "item": "game",
        "side": "all",
        "count": 1
    })
    phase_list_encoding = [json.dumps(phase) for phase in phase_list]
    xibie_redis.rpush(f"{REDIS_GAME_PREFIX}{game_id}-phase", *phase_list_encoding)
    xibie_redis.expire(f"{REDIS_GAME_PREFIX}{game_id}-phase", REDIS_GAME_EXPIRE)

    

async def exit_game(token: str):
    user_id = get_user_id_by_token(token)
    game_id = get_game_id(token)
    game_info = xibie_redis.hgetall(f'{REDIS_GAME_PREFIX}{game_id}')
    xibie_redis.hdel(f'{REDIS_USER_PREFIX}{user_id}', 'gameId')
    
    side = get_game_side(game_id, user_id)
    rival_side = 'player2' if side == 'player1' else 'player1' if side == 'player2' else None
    if rival_side:
        if 'wait' in game_info: # 准备阶段，空出位置
            xibie_redis.hdel(f'{REDIS_GAME_PREFIX}{game_id}', side)
        await ws_manager.broadcast(game_id, generate_event('exitGame', {'userid': user_id, 'side': side}))
        logger.info(f"用户：{user_id}，退出对局：{game_id}")

        logger.debug(f"{rival_side}")
        
        # 判断对手是否还在对局中
        if rival_side in game_info and game_id == xibie_redis.hget(f'{REDIS_USER_PREFIX}{game_info[rival_side]}', 'gameId'):
            # 对手还在对局中，房间继续存在
            pass
        else:
            # 对手也退出了，就关闭对局
            await clear_game_data(game_id)
            await ws_manager.broadcast(game_id, generate_event('closeGame', {'userid': user_id, 'side': 'player1'}))
    
    return True

async def clear_game_data(game_id):
    game_info = xibie_redis.hgetall(f'{REDIS_GAME_PREFIX}{game_id}')
    if 'player1' in game_info:
        player1 = game_info['player1']
        xibie_redis.hdel(f'{REDIS_USER_PREFIX}{player1}', 'gameId')
    if 'player2' in game_info:
        player2 = game_info['player2']
        xibie_redis.hdel(f'{REDIS_USER_PREFIX}{player2}', 'gameId')
        
    xibie_redis.delete(f'{REDIS_GAME_PREFIX}{game_id}-phase')
    xibie_redis.delete(f'{REDIS_GAME_PREFIX}{game_id}-player1-pet-picked')
    xibie_redis.delete(f'{REDIS_GAME_PREFIX}{game_id}-player2-pet-picked')
    xibie_redis.delete(f'{REDIS_GAME_PREFIX}{game_id}')
    async with delay_manager.get_game_lock(game_id):
        await delay_manager.cancel_timer(game_id)
        await delay_manager.stop_game(game_id)
        logger.info(f"关闭对局: {game_id}")
    
def get_game_state_unilateral(token):
    """获取game的非对称信息项目

    Args:
        token (_type_): _description_

    Returns:
        _type_: _description_
    """
    game_id = get_game_id(token)
    user_id = get_user_id_by_token(token)
    game_side = get_game_side(game_id, user_id)
    game_info = xibie_redis.hgetall(f"{REDIS_GAME_PREFIX}{game_id}")
    count_down = delay_manager.get_left_time_by_game_id(game_id)
    phase = (
        {
            "operation": "wait",
            "item": "game",
            "side": "all",
            "count": 1
        } if 'wait' in game_info
        else get_curr_phase_info(game_id)
    )
    state = {
        "game_id": game_id,
        "side": game_side,
        "count_down": count_down,
        "phase": phase,
        "rule_first": game_info['ruleFirst'],
        "rule_second": game_info['ruleSecond'],
    }

    if game_side == 'player1':
        if 'player1BothCount' in game_info:
            state["chance"] = {
                "both": game_info['player1BothCount'],
            }
        else:
            state["chance"] = {
                "hide": game_info['player1RollCount'],
                "roll": game_info['player1HideCount'],
            }
    elif game_side == 'player2':
        if 'player2BothCount' in game_info:
            state["chance"] = {
                "both": game_info['player2BothCount'],
            }
        else:
            state["chance"] = {
                "hide": game_info['player2RollCount'],
                "roll": game_info['player2HideCount'],
            }
    
    
    return state


def get_game_state_symmetry(token):
    """获取game的对称信息项目

    Args:
        token (_type_): _description_

    Returns:
        _type_: _description_
    """
    game_id = get_game_id(token)
    game_info = xibie_redis.hgetall(f"{REDIS_GAME_PREFIX}{game_id}")
    player1_info = xibie_redis.hgetall(f"{REDIS_USER_PREFIX}{game_info['player1']}") if 'player1' in game_info else {}
    player2_info = xibie_redis.hgetall(f"{REDIS_USER_PREFIX}{game_info['player2']}") if 'player2' in game_info else {}
    state = {
        "player1": {
            "userid": game_info['player1'] if 'player1' in game_info else None,
            "name": player1_info['nickname'] if 'nickname' in player1_info else "",
            "ready": True if 'player1Ready' in game_info else False,
            "suit_banned": game_info['player1SuitBanned'] if 'player1SuitBanned' in game_info else None,
            "type_banned": int(game_info['player1TypeBanned']) if 'player1TypeBanned' in game_info else None,
            "type_third": int(game_info['player1TypeThird']) if 'player1TypeThird' in game_info else None,
            "suit_picked": game_info['player1SuitPicked'] if 'player1SuitPicked' in game_info else None
        },
        "player2": {
            "userid": game_info['player2'] if 'player2' in game_info else None,
            "name": player2_info['nickname'] if 'nickname' in player2_info else "",
            "ready": True if 'player2Ready' in game_info else False,
            "suit_banned": game_info['player2SuitBanned'] if 'player2SuitBanned' in game_info else None,
            "type_banned": int(game_info['player2TypeBanned']) if 'player2TypeBanned' in game_info else None,
            "type_third": int(game_info['player2TypeThird']) if 'player2TypeThird' in game_info else None,
            "suit_picked": game_info['player2SuitPicked'] if 'player2SuitPicked' in game_info else None
        }
    }
    if xibie_redis.hexists(f"{REDIS_GAME_PREFIX}{game_id}", 'player1BothCount'):
        state['player1']["chance"] = {
            "both": game_info['player1BothCount'],
        }
    else:
        state['player1']["chance"] = {
            "hide": game_info['player1HideCount'],
            "roll": game_info['player1RollCount'],
        }

    if xibie_redis.hexists(f"{REDIS_GAME_PREFIX}{game_id}", 'player2BothCount'):
        state['player2']["chance"] = {
            "both": game_info['player2BothCount'],
        }
    else:
        state['player2']["chance"] = {
            "hide": game_info['player2HideCount'],
            "roll": game_info['player2RollCount'],
        }
    return state


def get_game_pets_pick(hidden_open, token: str):
    """获取双方的精灵pick

    Args:
        hidden_open (int): 是否显示暗手
        token (str): _description_

    Returns:
        _type_: _description_
    """
    game_id = get_game_id(token)
    user_id = get_user_id_by_token(token)
    game_side = get_game_side(game_id, user_id)

    player1_pick = xibie_redis.lrange(f"{REDIS_GAME_PREFIX}{game_id}-player1-pet-picked", 0, -1)
    player2_pick = xibie_redis.lrange(f"{REDIS_GAME_PREFIX}{game_id}-player2-pet-picked", 0, -1)
    player1_pick = [json.loads(pick) for pick in player1_pick] if player1_pick else []
    player2_pick = [json.loads(pick) for pick in player2_pick] if player2_pick else []
    pets_picked = {
        "player1": player1_pick,
        "player2": player2_pick,
    }

    rival_side = 'player2' if game_side == 'player1' else 'player1' if game_side == 'player2' else None
    if rival_side is None:
        # 应该是观众
        for pick in pets_picked['player1']:
            if 'hidden' in pick and pick['hidden']:
                if 'pet_id' in pick:
                    del pick['pet_id']
                if 'pet_origin_id' in pick:
                    del pick['pet_origin_id']
                if 'pet_head' in pick:
                    del pick['pet_head']
                if 'pet_name' in pick:
                    pick['pet_name'] = '暗手精灵'
                if 'ex_mark' in pick:
                    del pick['ex_mark']
        for pick in pets_picked['player2']:
            if 'hidden' in pick and pick['hidden']:
                if 'pet_id' in pick:
                    del pick['pet_id']
                if 'pet_origin_id' in pick:
                    del pick['pet_origin_id']
                if 'pet_head' in pick:
                    del pick['pet_head']
                if 'pet_name' in pick:
                    pick['pet_name'] = '暗手精灵'
                if 'ex_mark' in pick:
                    del pick['ex_mark']
    else:
        # 选手，默认只遮住对方
        for pick in pets_picked[rival_side]:
            if 'hidden' in pick and pick['hidden']:
                if 'pet_id' in pick:
                    del pick['pet_id']
                if 'pet_origin_id' in pick:
                    del pick['pet_origin_id']
                if 'pet_head' in pick:
                    del pick['pet_head']
                if 'pet_name' in pick:
                    pick['pet_name'] = '暗手精灵'
                if 'ex_mark' in pick:
                    del pick['ex_mark']
        if hidden_open == 0:
            # 遮住自己的暗手
            for pick in pets_picked[game_side]:
                if 'hidden' in pick and pick['hidden']:
                    if 'pet_id' in pick:
                        del pick['pet_id']
                    if 'pet_origin_id' in pick:
                        del pick['pet_origin_id']
                    if 'pet_head' in pick:
                        del pick['pet_head']
                    if 'pet_name' in pick:
                        pick['pet_name'] = '暗手精灵'
                    if 'ex_mark' in pick:
                        del pick['ex_mark']
    return pets_picked


async def get_enable_item(token, page_no, page_size):
    """获取当前阶段可以bp的item

    Args:
        token (_type_): _description_
        page_no (_type_): _description_
        page_size (_type_): _description_

    Returns:
        _type_: _description_
    """
    user_id = get_user_id_by_token(token)
    game_id = get_game_id(token)
    phase = get_curr_phase_info(game_id)
    if 'item' in phase:
        if phase['item'] == "pet_type":
            # 获取能bp的属系
            return await get_enable_pet_types(user_id, game_id, phase, page_no, page_size)
        elif phase['item'] == "suit":
            # 获取能bp的套装
            return await get_enable_suits(user_id, game_id, phase, page_no, page_size)
        elif phase['item'] == 'pet':
            # 获取能bp的精灵
            return await get_enable_pets(user_id, game_id, phase, page_no, page_size)
    else:
        return {
            "total": 0,
            "data": [],
            "item": None
        }
    
async def get_enable_pet_types(user_id, game_id, curr_phase, page_no, page_size):
    """获取当前阶段可以bp的属系

    Args:
        user_id (_type_): _description_
        game_id (_type_): _description_
        curr_phase (_type_): _description_
        page_no (_type_): _description_
        page_size (_type_): _description_

    Returns:
        _type_: _description_
    """
    async with get_mysql_session() as session:
        player1_type_banned = xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", 'player1TypeBanned')
        player2_type_banned = xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", 'player2TypeBanned')
        stmt = select(PetType).where(PetType.sub_type1.is_(None), PetType.sub_type2.is_(None)) # 子属系都是空，表明单属系
        if player1_type_banned:
            stmt = stmt.where(PetType.pet_type_id != int(player1_type_banned))
        if player2_type_banned:
            stmt = stmt.where(PetType.pet_type_id != int(player2_type_banned))
        count_stmt = select(func.count()).select_from(stmt)
        count_res = await session.execute(count_stmt)
        total = count_res.scalar()
        query_stmt = stmt.offset((page_no - 1) * page_size).limit(page_size)
        res = await session.execute(query_stmt)
        data = list(res.scalars().all())
        return {
            "total": total,
            "data": [item.to_dict() for item in data],
            "item": "pet_type"
        }


async def get_enable_suits(user_id, game_id, curr_phase, page_no, page_size):
    """获取当前阶段可以bp的套装

    Args:
        user_id (_type_): _description_
        game_id (_type_): _description_
        curr_phase (_type_): _description_
        page_no (_type_): _description_
        page_size (_type_): _description_

    Returns:
        _type_: _description_
    """
    async with get_mysql_session() as session:
        stmt = select(Suit)
        if curr_phase['operation'] == 'pick':
            # 选的时候才需要考虑被ban
            side = get_game_side(game_id, user_id)
            suit_banned = 0
            if side == 'player1':
                suit_banned = xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", 'player2SuitBanned')
            elif side == 'player2':
                suit_banned = xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", 'player1SuitBanned')
            suit_banned = int(suit_banned) if suit_banned else 0
            stmt = stmt.where(Suit.suit_id != suit_banned)

        count_stmt = select(func.count()).select_from(stmt)
        count_res = await session.execute(count_stmt)
        total = count_res.scalar()
        query_stmt = stmt.offset((page_no - 1) * page_size).limit(page_size)
        res = await session.execute(query_stmt)
        data = list(res.scalars().all())
        return {
            "total": total,
            "data": [item.to_dict() for item in data],
            "item": "suit"
        }


async def get_enable_pets(user_id, game_id, curr_phase, page_no, page_size):
    """获取当前阶段可以bp的精灵

    Args:
        user_id (_type_): _description_
        game_id (_type_): _description_
        curr_phase (_type_): _description_
        page_no (_type_): _description_
        page_size (_type_): _description_

    Returns:
        _type_: _description_
    """
    game_info = xibie_redis.hgetall(f"{REDIS_GAME_PREFIX}{game_id}")
    group_id = int(game_info['groupId'])
    pet_pick_count = curr_phase['count']
    side = get_game_side(game_id, user_id)
    async with get_mysql_session() as session:
        # 获取当前随机到的属系
        curr_pick = xibie_redis.lindex(f"{REDIS_GAME_PREFIX}{game_id}-{side}-pet-picked", pet_pick_count - 1)
        curr_pick = json.loads(curr_pick) if curr_pick else None
        if curr_pick is None:
            return {
                "total": 0,
                "data": [],
                "item": None
            }
        curr_type = curr_pick['pet_type']
        # 当前显示的精灵应该只存在被pick不存在被ban，左连接比赛组设置剔除公ban
        stmt = select(Pet).join(RaceGroupPetBanHide,
                                and_(Pet.pet_id == RaceGroupPetBanHide.pet_id,
                                     RaceGroupPetBanHide.group_id == group_id), isouter=True) \
                .join(PetType, Pet.pet_type == PetType.pet_type_id) \
            .where(RaceGroupPetBanHide.is_banned.is_(None), or_(Pet.pet_type == curr_type, PetType.sub_type1 == curr_type, PetType.sub_type2 == curr_type))
        count_stmt = select(func.count()).select_from(stmt)
        count_res = await session.execute(count_stmt)
        total = count_res.scalar()
        query_stmt = stmt.order_by(Pet.pet_origin_id.desc()).offset((page_no - 1) * page_size).limit(page_size)
        res = await session.execute(query_stmt)
        tmp = res.scalars().all()
        data = []
        for item in tmp:
            pet = item.to_dict()
            pet['pet_head'] = item.head_hs if item.head_hs else f"{item.pet_origin_id}.png"
            data.append(pet)

        return {
            "total": total,
            "data": data,
            "item": "pet"
        }
        
async def get_banhide_pets(token):
    game_id = get_game_id(token)
    group_id = int(xibie_redis.hget(f"{REDIS_GAME_PREFIX}{game_id}", 'groupId'))
    
    async with get_mysql_session() as session:
        stmt = select(RaceGroupPetBanHide).where(RaceGroupPetBanHide.group_id == group_id, RaceGroupPetBanHide.is_banned == 1)
        res = await session.execute(stmt)
        data = res.scalars().all()
        
        stmt2 = select(RaceGroupPetBanHide).where(RaceGroupPetBanHide.group_id == group_id, RaceGroupPetBanHide.is_hide_enable == 0)
        res2 = await session.execute(stmt2)
        data2 = res2.scalars().all()
        return {
            "ban": [item.pet_id for item in data],
            "hide_disable": [item.pet_id for item in data2]
        }
        
async def save_game_log(game_id):
    game_log = {
        "game_idx": f"{game_id}_{int(datetime.now().timestamp())}",
    }
    if not xibie_redis.exists(f"{REDIS_GAME_PREFIX}{game_id}"):
        game_log["error"] = '房间未找到'
        await xibie_mongo.game_logs.insert_one(game_log)
        return
    elif not xibie_redis.exists(f"{REDIS_GAME_PREFIX}{game_id}-player1-pet-picked"):
        game_log["error"] = '蓝方pick数据缺失'
        await xibie_mongo.game_logs.insert_one(game_log)
        return
    elif not xibie_redis.exists(f"{REDIS_GAME_PREFIX}{game_id}-player2-pet-picked"):
        game_log["error"] = '红方pick数据缺失'
        await xibie_mongo.game_logs.insert_one(game_log)
        return
    else:
        game_info = xibie_redis.hgetall(f"{REDIS_GAME_PREFIX}{game_id}")
        player1_pick = xibie_redis.lrange(f"{REDIS_GAME_PREFIX}{game_id}-player1-pet-picked", 0, -1)
        player2_pick = xibie_redis.lrange(f"{REDIS_GAME_PREFIX}{game_id}-player2-pet-picked", 0, -1)
        player1_pick = [json.loads(pick) for pick in player1_pick] if player1_pick else []
        player2_pick = [json.loads(pick) for pick in player2_pick] if player2_pick else []
        if xibie_redis.exists(f"{REDIS_USER_PREFIX}{game_info['player1']}"):
            player1_name = xibie_redis.hget(f"{REDIS_USER_PREFIX}{game_info['player1']}", 'nickname')
        else:
            player1_name = ''
        if xibie_redis.exists(f"{REDIS_USER_PREFIX}{game_info['player2']}"):
            player2_name = xibie_redis.hget(f"{REDIS_USER_PREFIX}{game_info['player2']}", 'nickname')
        else:
            player2_name = ''

        game_log['player1_name'] = player1_name
        game_log['player2_name'] = player2_name
        game_log['player1_pick'] = player1_pick
        game_log['player2_pick'] = player2_pick
        
        await xibie_mongo.game_logs.insert_one(game_log)
